/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "plugins/ecmascript/runtime/base/builtins_base.h"
#include "plugins/ecmascript/runtime/ecma_runtime_call_info.h"
#include "plugins/ecmascript/runtime/ecma_string.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/global_env.h"

#include "plugins/ecmascript/runtime/object_factory.h"
#include "plugins/ecmascript/tests/runtime/common/test_helper.h"

// NOLINTNEXTLINE(google-build-using-namespace)
using namespace ark::ecmascript;
// NOLINTNEXTLINE(google-build-using-namespace)
using namespace ark::ecmascript::builtins;
// NOLINTNEXTLINE(google-build-using-namespace)
using namespace ark::ecmascript::base;

namespace ark::test {
class BuiltinsGlobalTest : public testing::Test {
public:
    static void SetUpTestCase()
    {
        TestHelper::CreateEcmaVMWithScope(instance_, thread_, scope_);
    }

    static void TearDownTestCase()
    {
        TestHelper::DestroyEcmaVMWithScope(instance_, scope_);
    }

    static void TestEscape(const uint16_t *src, const uint32_t len, const char *dst)
    {
        ASSERT_NE(thread_, nullptr);
        ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
        JSHandle<EcmaString> str = factory->NewFromUtf16Literal(src, len);
        JSHandle<EcmaString> expected = factory->NewFromString(dst);

        auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 6);
        ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
        ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
        ecmaRuntimeCallInfo->SetCallArg(0, str.GetTaggedValue());

        [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
        JSTaggedValue result = global::Escape(ecmaRuntimeCallInfo.get());
        ASSERT_TRUE(result.IsString());

        ASSERT_EQ(expected->Compare(reinterpret_cast<EcmaString *>(result.GetRawData())), 0);
    }

    static void TestUnescape(const char *src, const char *res)
    {
        ASSERT_NE(thread_, nullptr);
        ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
        JSHandle<EcmaString> str = factory->NewFromString(src);
        JSHandle<EcmaString> expected = factory->NewFromString(res);

        auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 6);
        ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
        ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
        ecmaRuntimeCallInfo->SetCallArg(0, str.GetTaggedValue());

        [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
        JSTaggedValue result = global::Unescape(ecmaRuntimeCallInfo.get());
        ASSERT_TRUE(result.IsString());

        ASSERT_EQ(expected->Compare(reinterpret_cast<EcmaString *>(result.GetRawData())), 0);
    }

    static PandaVM *instance_;
    static EcmaHandleScope *scope_;
    static JSThread *thread_;
};
PandaVM *BuiltinsGlobalTest::instance_ = nullptr;
EcmaHandleScope *BuiltinsGlobalTest::scope_ = nullptr;
JSThread *BuiltinsGlobalTest::thread_ = nullptr;

TEST_F(BuiltinsGlobalTest, Escape)
{
    std::u16string str = u"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_+-./";
    TestEscape(reinterpret_cast<uint16_t *>(str.data()), str.size(), StringHelper::U16stringToString(str).c_str());
}

TEST_F(BuiltinsGlobalTest, Escape1)
{
    std::u16string str = u" / ";
    TestEscape(reinterpret_cast<uint16_t *>(str.data()), str.size(), "%20/%20");
}

TEST_F(BuiltinsGlobalTest, Escape2)
{
    std::u16string str = u"\n";
    TestEscape(reinterpret_cast<uint16_t *>(str.data()), str.size(), "%0A");
}

TEST_F(BuiltinsGlobalTest, Escape3)
{
    // NOLINTNEXTLINE(readability-magic-numbers)
    constexpr std::array<uint16_t, 1> C = {0x123};
    TestEscape(C.data(), C.size(), "%u0123");
}

TEST_F(BuiltinsGlobalTest, Escape4)
{
    // NOLINTNEXTLINE(readability-magic-numbers)
    constexpr std::array<uint16_t, 1> C = {0xabcd};
    TestEscape(C.data(), C.size(), "%uABCD");
}

TEST_F(BuiltinsGlobalTest, Escape5)
{
    constexpr std::array<uint16_t, 7> C = {0x41, 0x20, 0x42, 0x1234, 0, 0x20, 0x43};
    TestEscape(C.data(), C.size(), "A%20B%u1234%00%20C");
}

TEST_F(BuiltinsGlobalTest, Escape1000)
{
    ASSERT_NE(thread_, nullptr);
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();

    static const std::u16string UNESCAPED = u"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_+-./";

    // NOLINTNEXTLINE(readability-magic-numbers)
    for (uint16_t i = 0; i < 1000; ++i) {
        JSHandle<EcmaString> str = factory->NewFromUtf16Literal(&i, 1);

        auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 6);
        ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
        ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
        ecmaRuntimeCallInfo->SetCallArg(0, str.GetTaggedValue());

        [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
        JSTaggedValue result = global::Escape(ecmaRuntimeCallInfo.get());
        ASSERT_TRUE(result.IsString());

        bool isEqual = (str->Compare(reinterpret_cast<EcmaString *>(result.GetRawData())) == 0);

        if (UNESCAPED.find(i) == std::string::npos) {
            ASSERT_FALSE(isEqual);
        } else {
            ASSERT_TRUE(isEqual);
        }
    }
}

TEST_F(BuiltinsGlobalTest, Unescape1000)
{
    ASSERT_NE(thread_, nullptr);
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();

    // NOLINTNEXTLINE(readability-magic-numbers)
    for (uint16_t i = 0; i < 1000; i += 10) {
        // NOLINTNEXTLINE(readability-magic-numbers)
        std::array<uint16_t, 10> c = {static_cast<uint16_t>(i), static_cast<uint16_t>(i + 1),
                                      // NOLINTNEXTLINE(readability-magic-numbers)
                                      static_cast<uint16_t>(i + 2), static_cast<uint16_t>(i + 3),
                                      // NOLINTNEXTLINE(readability-magic-numbers)
                                      static_cast<uint16_t>(i + 4), static_cast<uint16_t>(i + 5),
                                      // NOLINTNEXTLINE(readability-magic-numbers)
                                      static_cast<uint16_t>(i + 6), static_cast<uint16_t>(i + 7),
                                      // NOLINTNEXTLINE(readability-magic-numbers)
                                      static_cast<uint16_t>(i + 8), static_cast<uint16_t>(i + 9)};
        JSHandle<EcmaString> str = factory->NewFromUtf16Literal(c.data(), c.size());

        auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 6);
        ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
        ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
        ecmaRuntimeCallInfo->SetCallArg(0, str.GetTaggedValue());

        [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
        JSTaggedValue resultEscape = global::Escape(ecmaRuntimeCallInfo.get());

        auto ecmaRuntimeCallInfo2 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 6);
        ecmaRuntimeCallInfo2->SetFunction(JSTaggedValue::Undefined());
        ecmaRuntimeCallInfo2->SetThis(JSTaggedValue::Undefined());
        ecmaRuntimeCallInfo2->SetCallArg(0, resultEscape);

        [[maybe_unused]] auto prev2 = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo2.get());
        JSTaggedValue result = global::Unescape(ecmaRuntimeCallInfo2.get());

        ASSERT_TRUE(result.IsString());
        ASSERT_EQ(str->Compare(reinterpret_cast<EcmaString *>(result.GetRawData())), 0);
    }
}

TEST_F(BuiltinsGlobalTest, Unescape1)
{
    TestUnescape("%41%4A%4a", "AJJ");
}

TEST_F(BuiltinsGlobalTest, Unescape2)
{
    TestUnescape("%U1234", "%U1234");
}

TEST_F(BuiltinsGlobalTest, Unescape3)
{
    TestUnescape("%%20", "% ");
}

TEST_F(BuiltinsGlobalTest, Unescape4)
{
    TestUnescape("%%%20", "%% ");
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape1)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "%";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape2)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "%4";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape3)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "%u";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape4)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "%u4";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape5)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "%u44";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape6)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "%u444";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape7)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "%4z";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape8)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "%uzzzz";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape9)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "%u4zzz";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape10)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "%u44zz";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape11)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "%u444z";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape12)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "%4+";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape13)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "%u++++";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape14)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "%u4+++";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape15)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "%u44++";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape16)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "%u444+";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape17)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "foo%4+";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape18)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "foo%u++++";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape19)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "foo%u4+++";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape20)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "foo%u44++";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape21)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "foo%u444+";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape22)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "foo%4+bar";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape23)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "foo%u++++bar";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape24)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "foo%u4+++bar";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape25)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "foo%u44++bar";
    TestUnescape(s, s);
}

TEST_F(BuiltinsGlobalTest, MalformedUnescape26)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char s[] = "foo%u444+bar";
    TestUnescape(s, s);
}

}  // namespace ark::test
